/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wookie.proxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.log4j.Logger;

/**
 * A class using HttpClient to handle HTTP requests & manipulate the responses
 */
public class ProxyClient {

    static Logger fLogger = Logger.getLogger(ProxyClient.class.getName());
    private String fContentType = "text/plain";
    private String fProxyUsername = null;
    private String fProxyPassword = null;
    private String fBase64Auth = null;
    private NameValuePair[] parameters = null;
    private boolean fUseProxyAuthentication = false;

    @SuppressWarnings("unchecked")
    public ProxyClient(HttpServletRequest request) {
//        Properties systemProperties = System.getProperties();
//        systemProperties.setProperty("http.proxyHost", "wwwcache.open.ac.uk");
//        systemProperties.setProperty("http.proxyPort", "80");
        String proxyUserName = request.getParameter("username");
        String proxyPassword = request.getParameter("password");
        String base64Auth = request.getHeader("Authorization");
        if (proxyUserName != null && proxyPassword != null) {
            this.setProxyAuthConfig(proxyUserName, proxyPassword);
        }
        if (base64Auth != null) {
            this.setBase64AuthConfig(base64Auth);
        }
        filterParameters(request.getParameterMap());
        fContentType = request.getContentType();
    }

    private void setProxyAuthConfig(String username, String password) {
        fUseProxyAuthentication = true;
        fProxyUsername = username;
        fProxyPassword = password;
    }

    private void setBase64AuthConfig(String base64Auth) {
        fUseProxyAuthentication = true;
        fBase64Auth = base64Auth;
    }

    public String getCType() {
        return fContentType;
    }

    public String get(String url, Configuration properties) throws Exception {
        fLogger.debug("GET from " + url); //$NON-NLS-1$
        Properties systemProperties = System.getProperties();
        systemProperties.setProperty("http.proxyHost", "wwwcache.open.ac.uk");
        systemProperties.setProperty("http.proxyPort", "80");

        GetMethod method = new GetMethod(url);
        method.setDoAuthentication(true);
        return executeMethod(method, properties);
    }

    public String post(String uri, String xmlData, Configuration properties) throws Exception {
        fLogger.debug("POST to " + uri); //$NON-NLS-1$
        PostMethod method = new PostMethod(uri);
        method.setDoAuthentication(true);

        if (this.parameters.length > 0) {
            method.addParameters(this.parameters);
        } else {
            method.setRequestEntity(new StringRequestEntity(xmlData, "text/xml", "UTF8"));//$NON-NLS-1$  //$NON-NLS-2$
        }

        return executeMethod(method, properties);
    }

    /**
     * Processes the parameters passed through to the request,
     * removing the parameters used by the proxy itself
     * @return
     */
    private void filterParameters(Map<Object, Object> umap) {
        Map<Object, Object> map = new HashMap<Object, Object>(umap);
        map.remove("instanceid_key");
        map.remove("url");
        ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
        for (Object key : map.keySet().toArray()) {
            for (String value : (String[]) map.get(key)) {
                NameValuePair param = new NameValuePair();
                param.setName((String) key);
                param.setValue(value);
                params.add(param);
            }
        }
        parameters = params.toArray(new NameValuePair[params.size()]);
    }

    private String executeMethod(HttpMethod method, Configuration properties) throws Exception, AuthenticationException {
        // Execute the method.
        try {
            HttpClient client = new HttpClient();

            // set the clients proxy values if needed
            ConnectionsPrefsManager.setProxySettings(client, properties);

            if (fUseProxyAuthentication) {
                if (fBase64Auth != null) {
                    method.setRequestHeader("Authorization", fBase64Auth);
                } else {
                    List<String> authPrefs = new ArrayList<String>(2);
                    authPrefs.add(AuthPolicy.DIGEST);
                    authPrefs.add(AuthPolicy.BASIC);
                    client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
                    // send the basic authentication response even before the server gives an unauthorized response
                    client.getParams().setAuthenticationPreemptive(true);
                    // Pass our credentials to HttpClient
                    client.getState().setCredentials(
                            new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
                            new UsernamePasswordCredentials(fProxyUsername, fProxyPassword));
                }
            }

            // Add user language to http request in order to notify server of user's language
            Locale locale = Locale.getDefault();

            method.setRequestHeader("Accept-Language", locale.getLanguage()); //$NON-NLS-1$
            method.removeRequestHeader("Content-Type");
            //method.setRequestHeader("Content-Type","application/json");
            //method.setRequestHeader("Referer", "");
            //method.removeRequestHeader("Referer");
            method.setRequestHeader("Accept","*/*");

            int statusCode = client.executeMethod(method);

            //System.out.println("response="+method.getResponseBodyAsString());
            //System.out.println("method="+method.toString());
            //System.out.println("response="+method.getResponseBodyAsStream());


            if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_CREATED) {
                Header hType = method.getResponseHeader("Content-Type");
                if (hType != null) {
                    fContentType = hType.getValue();
                }
                // for now we are only expecting Strings
                //return method.getResponseBodyAsString();
                return readFully(new InputStreamReader(method.getResponseBodyAsStream(), "UTF-8"));
            } else if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED || statusCode == HttpStatus.SC_UNAUTHORIZED) {
                throw new AuthenticationException("Authentication failed:" + method.getStatusLine() + ' ' + method.getURI() + ' ' + method.getStatusText());
            } else {
                throw new Exception("Method failed: " + method.getStatusLine() + ' ' + method.getURI() + ' ' + method.getStatusText()); //$NON-NLS-1$
            }
        } catch (IOException e) {
            throw e;
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
    }

    /**
     * This is supposed to be the correct way to read the response instead of using getResponseBody() - which gives a runtime warning;
     *
     * See - http://mail-archives.apache.org/mod_mbox/jakarta-httpclient-user/200411.mbox/%3c1101558111.4070.22.camel@localhost.localdomain%3e
     * @param input
     * @return
     * @throws IOException
     */
    private String readFully(Reader input) throws IOException {
        BufferedReader bufferedReader = input instanceof BufferedReader ? (BufferedReader) input
                : new BufferedReader(input);
        StringBuffer result = new StringBuffer();
        char[] buffer = new char[4 * 1024];
        int charsRead;
        while ((charsRead = bufferedReader.read(buffer)) != -1) {
            result.append(buffer, 0, charsRead);
        }
        return result.toString();
    }
}
